﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace LocalOverride.Proxy
{
    public class HttpProxy
    {
        private readonly int port;

        public HttpProxy(int port)
        {
            this.port = port;
        }

        private Func<string, InterceptResult> intercept;
        private TcpListener myListener;

        public void Start(Func<string, InterceptResult> func)
        {
            intercept = func;

            try
            {
                //start listing on the given port

                myListener = new TcpListener(IPAddress.Any, port);
                myListener.Start();
                Console.WriteLine("Web Server Running... Press ^C to Stop...");

                //start the thread which calls the method 'StartListen'

                var th = new Thread(StartListen);
                th.Start();
            }
            catch (Exception e)
            {
                Console.WriteLine("An Exception Occurred while Listening :"
                                   + e.ToString());
            }
        }

        private bool process = true;
        private void StartListen()
        {
            myListener.BeginAcceptSocket(OnAccept, null);
        }

        private void OnAccept(IAsyncResult ar)
        {
            try
            {
                Socket mySocket = myListener.EndAcceptSocket(ar);

                var connection = new ProxyConnection(mySocket, intercept);

                var thread = new Thread(connection.Run);
                thread.Start();

            }
            catch (ObjectDisposedException ex)
            {
                // connection disposed...that's life I guess
                Console.WriteLine(ex);
            }
            catch (SocketException ex)
            {
                //underlying socket went bad...that's life I guess
                Console.WriteLine(ex);
            }


            if (process)
            {
                myListener.BeginAcceptSocket(OnAccept, null);
            }
        }


        public void Stop()
        {
            process = false;
            myListener.Stop();
        }
    }

    /// <summary>
    /// Code loosly based upon: http://www.codeproject.com/KB/IP/mywebserver.aspx
    /// </summary>
    internal class ProxyConnection
    {
        private readonly Socket clientSocket;
        private readonly Func<string, InterceptResult> intercept;

        public ProxyConnection(Socket clientSocket, Func<string, InterceptResult> intercept)
        {
            this.clientSocket = clientSocket;
            this.intercept = intercept;

            mimeTypes.Add(".aspx", "text/html");
            mimeTypes.Add(".js", "text/javascript");
            mimeTypes.Add(".css", "text/css");
            mimeTypes.Add(".jpg", "image/jpeg");
            mimeTypes.Add(".html", "text/html");
            mimeTypes.Add(".png", "image/png");
        }

        Dictionary<string, string> mimeTypes = new Dictionary<string, string>();

        public void Run()
        {
            String sRequest;
            try
            {
                Console.WriteLine("Socket Type " + clientSocket.SocketType);
                if (clientSocket.Connected)
                {
                    Console.WriteLine("Client Connected");

                    Byte[] requestBuffer = new Byte[1024];
                    clientSocket.Receive(requestBuffer, requestBuffer.Length, 0);


                    string request = Encoding.ASCII.GetString(requestBuffer);


                    if (!request.StartsWith("GET"))
                    {
                        Console.WriteLine("TODO: Add support for POSTS etc");
                        clientSocket.Close();
                        return;
                    }


                    // Look for HTTP request
                    int httpHeaderIndex = request.IndexOf("HTTP", 1);


                    // Get the HTTP text and version e.g. it will return "HTTP/1.1"
                    string httpVersion = request.Substring(httpHeaderIndex, 8);


                    // Extract the Requested Type and Requested file/directory
                    sRequest = request.Substring(0, httpHeaderIndex - 1);

                    var url = sRequest.Replace("GET ", "");
                    var result = intercept(url);

                    if (result.ResultType == InterceptResultType.String)
                    {
                        var message = result.Response;
                        SendHeader(httpVersion, "", message.Length, " 200 OK", clientSocket);

                        //Send to the browser

                        SendToBrowser(message, clientSocket);

                        clientSocket.Close();

                        return;
                    }

                    if (result.ResultType == InterceptResultType.File)
                    {
                        var fileName = result.FileName;
                        int iTotBytes = 0;

                        try
                        {
                            FileStream fs = new FileStream(fileName,
                                                           FileMode.Open, FileAccess.Read,
                                                           FileShare.Read);
                            // Create a reader that can read bytes from the FileStream.

                            var extension = Path.GetExtension(fileName);
                            string mimeType;

                            if (mimeTypes.ContainsKey(extension))
                            {
                                mimeType = mimeTypes[extension];
                            }
                            else
                            {
                                mimeType = "text/html";
                            }

                            SendHeader(httpVersion, mimeType, fs.Length, " 200 OK", clientSocket);


                            BinaryReader reader = new BinaryReader(fs);
                            byte[] bytes = new byte[fs.Length];
                            int read;
                            while ((read = reader.Read(bytes, 0, bytes.Length)) != 0)
                            {
                                // Read from the file and write the data to the network
                                if (clientSocket.Connected)
                                    clientSocket.Send(bytes, read, 0);
                                //sResponse = sResponse + Encoding.ASCII.GetString(bytes, 0, read);

                                iTotBytes = iTotBytes + read;

                            }
                            reader.Close();
                            fs.Close();
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex);
                        }
                    }

                    clientSocket.Close();
                }


            }
            catch (Exception ex)
            {
                Console.WriteLine("Some Exception: " + ex);
            }
            finally
            {
                if (clientSocket != null)
                    if (clientSocket.Connected)
                        clientSocket.Close();
            }
        }

        public void SendHeader(string httpVersion, string mimeType,
         long contentLength, string httpStatusCode, Socket mySocket)
        {

            StringBuilder header = new StringBuilder();

            // if Mime type is not provided set default to text/html

            if (mimeType.Length == 0)
            {
                mimeType = "text/html";  // Default Mime Type is text/html

            }

            header.Append(httpVersion + httpStatusCode + "\r\n");
            header.Append("Server: localoverride-proxy\r\n");
            header.Append("Content-Type: " + mimeType + "\r\n");
            header.Append("Accept-Ranges: bytes\r\n");
            header.Append("Content-Length: " + contentLength + "\r\n\r\n");

            Byte[] headerBuffer = Encoding.ASCII.GetBytes(header.ToString());

            SendToBrowser(headerBuffer, mySocket);
        }

        public void SendToBrowser(String content, Socket socket)
        {
            SendToBrowser(Encoding.ASCII.GetBytes(content), socket);
        }


        public void SendToBrowser(Byte[] contentBuffer, Socket socket)
        {
            if (socket.Connected)
            {
                socket.Send(contentBuffer, contentBuffer.Length, 0);
            }
            else
                throw new ConnectionException("Connection Dropped");
        }

    }

    internal class ConnectionException : Exception
    {
        public ConnectionException(string message)
            : base(message)
        {

        }
    }

    public class InterceptResult
    {
        public InterceptResultType ResultType { get; set; }

        public string Response { get; set; }

        public string FileName { get; set; }
    }

    public enum InterceptResultType
    {
        File,
        String,
        PassThrough
    }
}